﻿using System;
using UnityEngine;

namespace Gj
{
    [Serializable]
    public struct HexCoordinates
    {
        [SerializeField] private int x, z;

        private HexCoordinates(int x, int z, HexMetrics metrics, bool wrap)
        {
            Metrics = metrics;

            if (wrap && Metrics.Wrapping)
            {
                var oX = x + z / 2;
                if (oX < 0)
                    x -= Metrics.wrapSize;
                else if (oX >= Metrics.wrapSize) x += Metrics.wrapSize;
            }

            this.x = x;
            this.z = z;
        }

        public HexMetrics Metrics { get; }

        private int Y => -x - z;

        public Vector3Int ToOffset()
        {
            var offset = new Vector3Int(x, 0, z + x / 2 * (x >= 0 ? 1 : -1));
            if (Metrics.Mode == HexMode.Column)
            {
                var t = offset.x;
                offset.x = offset.z;
                offset.z = t;
            }

            return offset;
        }

        public Vector3 ToPosition()
        {
            var metrics = Metrics;

            var iX = x;
            var iZ = z;
            if (Metrics.Wrapping)
            {
                var oX = x + z / 2;
                if (oX < 0)
                    iX += Metrics.wrapSize;
                else if (oX >= Metrics.wrapSize) iX -= Metrics.wrapSize;
            }

            var position = Vector3.zero;
            position.z = (iZ + iX * 0.5f * (x >= 0 ? 1 : -1)) * metrics.InnerDiameter;
            position.x = iX * (metrics.OuterRadius * 1.5f);
            if (Metrics.Mode == HexMode.Column)
            {
                var t = position.x;
                position.x = position.z;
                position.z = t;
            }

            return position;
        }

        public int DistanceTo(HexCoordinates other)
        {
            if (other.Metrics != Metrics) return 0;
            var xy =
                (x < other.x ? other.x - x : x - other.x) +
                (Y < other.Y ? other.Y - Y : Y - other.Y);

            if (Metrics.Wrapping)
            {
                other.x += Metrics.wrapSize;
                var xyWrapped =
                    (x < other.x ? other.x - x : x - other.x) +
                    (Y < other.Y ? other.Y - Y : Y - other.Y);
                if (xyWrapped < xy)
                {
                    xy = xyWrapped;
                }
                else
                {
                    other.x -= 2 * Metrics.wrapSize;
                    xyWrapped =
                        (x < other.x ? other.x - x : x - other.x) +
                        (Y < other.Y ? other.Y - Y : Y - other.Y);
                    if (xyWrapped < xy) xy = xyWrapped;
                }
            }

            return (xy + (z < other.z ? other.z - z : z - other.z)) / 2;
        }

        public static HexCoordinates FromOffset(Vector2Int offset, HexMetrics metrics = null)
        {
            return FromOffset(offset.x, offset.y, metrics);
        }

        public static HexCoordinates FromOffset(int x, int z, HexMetrics metrics = null)
        {
            if (metrics == null) metrics = HexMetrics.Default;
            if (metrics.Mode == HexMode.Column)
            {
                var t = x;
                x = z;
                z = t;
            }

            return new HexCoordinates(x, z - x / 2 * (x >= 0 ? 1 : -1), metrics, false);
        }

        public static HexCoordinates FromPosition(Vector3 position, HexMetrics metrics = null)
        {
            if (metrics == null) metrics = HexMetrics.Default;

            if (metrics.Mode == HexMode.Column)
            {
                var t = position.x;
                position.x = position.z;
                position.z = t;
            }

            var x = position.x / (metrics.OuterRadius * 1.5f);
            var z = position.z / metrics.InnerDiameter - x * 0.5f * (x >= 0 ? 1 : -1);

            var iX = Mathf.RoundToInt(x);
            var iZ = Mathf.RoundToInt(z);
            var iY = Mathf.RoundToInt(-x - z);

            if (iX + iY + iZ != 0)
            {
                var dX = Mathf.Abs(x - iX);
                var dZ = Mathf.Abs(z - iZ);
                var dY = Mathf.Abs(-x - z - iY);

                if (dX > dY && dX > dZ)
                    iX = -iY - iZ;
                else if (dZ > dY) iZ = -iX - iY;
            }

            return new HexCoordinates(iX, iZ, metrics, true);
        }
    }
}